package sunyu.tools.redis;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.nutz.lang.Lang;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import redis.clients.jedis.*;
import sunyu.tools.common.JsonTools;

import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Redis工具类
 *
 * @author 孙宇
 */
@Component
public class RedisTools {

    public static final String defaultName = "main";
    private static final ConcurrentHashMap<String, JedisPool> jedisPoolMap = new ConcurrentHashMap<>();//单机的jedis连接池
    private static final ConcurrentHashMap<String, JedisCluster> jedisClusterMap = new ConcurrentHashMap<>();//集群的jedisCluster连接池
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Resource(name = "jsonTools")
    private JsonTools jsonTools;


    @PreDestroy
    public void destroy() {
        logger.info("销毁所有jedis/jedisCluster连接池开始");
        for (Map.Entry<String, JedisPool> entry : jedisPoolMap.entrySet()) {
            entry.getValue().destroy();
        }
        for (Map.Entry<String, JedisCluster> entry : jedisClusterMap.entrySet()) {
            try {
                entry.getValue().close();
            } catch (IOException e) {
                logger.error(ExceptionUtils.getStackTrace(e));
            }
        }
        logger.info("销毁所有jedis/jedisCluster连接池结束");
    }

    /**
     * 添加一个jedisPool
     *
     * @param jedisPoolName
     * @param maxTotal
     * @param maxIdle
     * @param maxWaitMillis
     * @param testOnBorrow
     * @param connectionTimeout
     * @param host
     * @param port
     * @param password
     */
    public void addJedisPool(String jedisPoolName,
                             Integer maxTotal,
                             Integer maxIdle,
                             Integer maxWaitMillis,
                             Boolean testOnBorrow,
                             Integer connectionTimeout,
                             String host,
                             Integer port,
                             String password) {
        logger.info("添加一个jedis连接池：{}", jedisPoolName);
        if (jedisPoolMap.containsKey(jedisPoolName)) {
            logger.error("{}已存在!", jedisPoolName);
        } else {
            JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
            jedisPoolConfig.setMaxTotal(maxTotal);
            jedisPoolConfig.setMaxIdle(maxIdle);
            jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
            jedisPoolConfig.setTestOnBorrow(testOnBorrow);
            JedisPool jedisPool = null;
            if (StringUtils.isNotBlank(password)) {
                jedisPool = new JedisPool(jedisPoolConfig, host, port, connectionTimeout, password);
            } else {
                jedisPool = new JedisPool(jedisPoolConfig, host, port, connectionTimeout);
            }
            jedisPoolMap.put(jedisPoolName, jedisPool);
        }
    }

    /**
     * 添加一个jedisCluster
     *
     * @param jedisClusterName
     * @param maxTotal
     * @param maxIdle
     * @param maxWaitMillis
     * @param testOnBorrow
     * @param cluster
     */
    public void addJedisCluster(String jedisClusterName,
                                Integer maxTotal,
                                Integer maxIdle,
                                Integer maxWaitMillis,
                                Boolean testOnBorrow,
                                String cluster) {
        logger.info("添加一个jedis cluster连接池：{}", jedisClusterName);
        if (jedisClusterMap.containsKey(jedisClusterName)) {
            logger.error("{}已存在！", jedisClusterName);
        } else {
            JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
            jedisPoolConfig.setMaxTotal(maxTotal);
            jedisPoolConfig.setMaxIdle(maxIdle);
            jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
            jedisPoolConfig.setTestOnBorrow(testOnBorrow);
            Set<HostAndPort> jedisClusterNodes = new HashSet<>();
            List<Map> clusterList = jsonTools.toMapList(cluster);
            if (clusterList != null && clusterList.size() > 0) {
                for (Map<String, String> c : clusterList) {
                    jedisClusterNodes.add(new HostAndPort(c.get("host"), Integer.parseInt(c.get("port"))));
                }
                JedisCluster jedisCluster = new JedisCluster(jedisClusterNodes, jedisPoolConfig);
                jedisClusterMap.put(jedisClusterName, jedisCluster);
            }
        }
    }

    /**
     * 获得一个jedisPool
     *
     * @param jedisPoolName
     * @return
     */
    public JedisPool getJedisPool(String jedisPoolName) {
        return jedisPoolMap.get(jedisPoolName);
    }

    /**
     * 获得一个jedis
     *
     * @param jedisPoolName
     * @return
     */
    public Jedis getJedis(String jedisPoolName) {
        return getJedisPool(jedisPoolName).getResource();
    }

    /**
     * 获得一个JedisCluster
     *
     * @param jedisClusterName
     * @return
     */
    public JedisCluster getJedisCluster(String jedisClusterName) {
        return jedisClusterMap.get(jedisClusterName);
    }

    /**
     * 执行jedis指令
     *
     * @param jedisPoolName
     * @param action
     * @param <T>
     * @return
     */
    public <T> T execute(String jedisPoolName,
                         JedisCallback<T> action) {
        try (Jedis jedis = getJedis(jedisPoolName)) {
            return action.doInJedis(jedis);
        } catch (Throwable throwable) {
            throw Lang.wrapThrow(throwable);
        }
    }

    /**
     * 执行集群指令
     *
     * @param jedisClusterName
     * @param action
     * @param <T>
     * @return
     */
    public <T> T clusterExecute(String jedisClusterName,
                                JedisClusterCallback<T> action) {
        JedisCluster jedis = getJedisCluster(jedisClusterName);
        try {
            return action.doInJedisCluster(jedis);
        } catch (Throwable throwable) {
            throw Lang.wrapThrow(throwable);
        }
    }

    /**
     * 由于JedisCluster没有实现keys操作，这里自己实现以下
     *
     * @param pattern
     * @return
     */
    public Set<String> clusterKeys(String jedisClusterName,
                                   String pattern) {
        Set<String> keys = new HashSet<>();
        Map<String, JedisPool> clusterNodes = getJedisCluster(jedisClusterName).getClusterNodes();
        for (String k : clusterNodes.keySet()) {
            JedisPool jp = clusterNodes.get(k);
            try (Jedis connection = jp.getResource()) {
                keys.addAll(connection.keys(pattern));
            } catch (Exception e) {
                logger.error(ExceptionUtils.getStackTrace(e));
            }
        }
        return keys;
    }

    public Set<byte[]> clusterKeys(String jedisClusterName,
                                   byte[] pattern) {
        Set<byte[]> keys = new HashSet<>();
        Map<String, JedisPool> clusterNodes = getJedisCluster(jedisClusterName).getClusterNodes();
        for (Object k : clusterNodes.keySet()) {
            JedisPool jp = clusterNodes.get(k);
            try (Jedis connection = jp.getResource()) {
                keys.addAll(connection.keys(pattern));
            } catch (Exception e) {
                logger.error(ExceptionUtils.getStackTrace(e));
            }
        }
        return keys;
    }


    public void clusterFlushDB(String jedisClusterName) {
        Map<String, JedisPool> clusterNodes = this.getJedisCluster(jedisClusterName).getClusterNodes();
        for (Object k : clusterNodes.keySet()) {
            JedisPool jp = clusterNodes.get(k);
            try (Jedis connection = jp.getResource()) {
                connection.flushDB();
            } catch (Exception e) {
                logger.error(ExceptionUtils.getStackTrace(e));
            }
        }
    }

    public Long clusterDbSize(String jedisClusterName) {
        Long total = 0L;
        Map<String, JedisPool> clusterNodes = this.getJedisCluster(jedisClusterName).getClusterNodes();
        for (Object k : clusterNodes.keySet()) {
            JedisPool jp = clusterNodes.get(k);
            try (Jedis connection = jp.getResource()) {
                total += connection.dbSize();
            } catch (Exception e) {
                logger.error(ExceptionUtils.getStackTrace(e));
            }
        }
        return total;
    }

}
